package pl.edu.mimuw.irs.core.model.petrinet;

import java.util.ArrayList;
import java.util.List;

import pl.edu.mimuw.irs.core.exception.InternalException;
import pl.edu.mimuw.irs.core.exception.InternalException.Cause;
import pl.edu.mimuw.irs.core.global.IDManager;
import pl.edu.mimuw.irs.core.util.RelativePosition;

/**
 * @author Jakub Rauch
 * Created on: 2009-05-07
 * 
 * Arcs are directed edges. It can connect two nodes of different type: {@link ITransition} -> {@link IPlace} or the 
 * opposite. There cannot be more than one arc between the same two nodes pointing the same direction. Arcs are 
 * immutable in a matter of nodes. Weight of an arc cannot be negative.
 */
public class Arc extends Element {
	
	private static final long serialVersionUID = 7893291312569246153L;

	// *** Properties
	
	public static final String VERTICES = "VERTICES";
	
	// *** Attributes
	
	private INode source;
	private INode target;
	private IPlace place;
	private ITransition transition;
	private int weight;
	private List<RelativePosition> vertices;
	// Getters and setters
	public INode getSource() {
		return source;
	}
	public INode getTarget() {
		return target;
	}
	public IPlace getPlace() {
		return place;
	}
	public ITransition getTransition() {
		return transition;
	}
	public int getWeight() {
		return weight;
	}
	public void setWeight(int weight) {
		this.weight = weight;
	}
	public List<RelativePosition> getVertices() {
		if (this.vertices == null) this.vertices = new ArrayList<RelativePosition>();
		return vertices;
	}
	public void setVertices(List<RelativePosition> vertices) {
		Object oldValue = this.vertices;
		this.vertices = vertices;
		getListeners().firePropertyChange(VERTICES, oldValue, vertices);
	}
	
	// *** Constructors
	
	/**
	 * The general constructor.
	 */
	public Arc(IDManager idManager, INode source, INode target, int weight) {
		super(idManager);
		
		if (weight < 0) throw new InternalException(Cause.PETRI_NET_NEGATIVE_WEIGHT_ARC);
		else this.weight = weight;
		
		this.source = source;
		this.target = target;
		if ((source instanceof IPlace) && (target instanceof ITransition)) {
			this.place = (IPlace) source;
			this.transition = (ITransition) target;
		} else if ((source instanceof ITransition) && (target instanceof IPlace)) {
			this.place = (IPlace) target;
			this.transition = (ITransition) source;
		} else {
			throw new InternalException(Cause.PETRI_NET_UNSUPPORTED_ARC_NODES, source, target);
		}
		
		place.addArc(this);
		transition.addArc(this);
	}
	
	public Arc(IDManager idManager, INode source, INode target) {
		this(idManager, source, target, 1);
	}

	// *** Methods
	
	/**
	 * Arc is not active when its source is a place and place does not contain
	 * as many tokens as required by this arc (weight). Otherwise it's active.
	 * @return
	 */
	public boolean isActive() {
		if (getSource() == getPlace()) {
			return getPlace().getTokens() >= getWeight();
		} else {
			return true;
		}
	}
	
	/**
	 * Executes this arc - depending on type removes or adds tokens to a place.
	 */
	public void invoke() {
		if (getSource() == getPlace()) {
			getPlace().removeTokens(weight);
		} else if (getTarget() == getPlace()) {
			getPlace().addTokens(weight);
		} else {
			throw new InternalException(Cause.PETRI_NET_UNSUPPORTED_ARC_NODES, source, target);
		}
	}
	
	public void destroy() {
		IPlace place = getPlace();
		ITransition transition = getTransition();
		this.place = null;
		this.transition = null;
		this.source = null;
		this.target = null;
		if (place != null) place.removeArc(this);
		if (transition != null) transition.removeArc(this);
	}
	
	// *** Enums
	
	public enum Direction {
		TRANSITION_PLACE,
		PLACE_TRANSITION
	}
	
	@Override
	public String toString() {
		return "{" + getSource() + "-->" + getTarget() + "}";
	}
}
